// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package qunar.tc.decompiler.code.cfg;

import qunar.tc.decompiler.code.Instruction;
import qunar.tc.decompiler.code.InstructionSequence;
import qunar.tc.decompiler.code.SimpleInstructionSequence;
import qunar.tc.decompiler.main.DecompilerContext;
import qunar.tc.decompiler.modules.decompiler.decompose.IGraphNode;

import java.util.ArrayList;
import java.util.List;

public class BasicBlock implements IGraphNode {

    // *****************************************************************************
    // public fields
    // *****************************************************************************

    public int id;
    public int mark = 0;

    // *****************************************************************************
    // private fields
    // *****************************************************************************

    private InstructionSequence seq = new SimpleInstructionSequence();

    private final List<BasicBlock> preds = new ArrayList<>();
    private final List<BasicBlock> succs = new ArrayList<>();
    private final List<Integer> instrOldOffsets = new ArrayList<>();
    private final List<BasicBlock> predExceptions = new ArrayList<>();
    private final List<BasicBlock> succExceptions = new ArrayList<>();

    public BasicBlock(int id) {
        this.id = id;
    }

    // *****************************************************************************
    // public methods
    // *****************************************************************************

    @Override
    @SuppressWarnings("MethodDoesntCallSuperMethod")
    public BasicBlock clone() {
        BasicBlock block = new BasicBlock(id);

        block.setSeq(seq.clone());
        block.instrOldOffsets.addAll(instrOldOffsets);

        return block;
    }

    public Instruction getInstruction(int index) {
        return seq.getInstr(index);
    }

    public Instruction getLastInstruction() {
        if (seq.isEmpty()) {
            return null;
        } else {
            return seq.getLastInstr();
        }
    }

    public Integer getOldOffset(int index) {
        if (index < instrOldOffsets.size()) {
            return instrOldOffsets.get(index);
        } else {
            return -1;
        }
    }

    public int size() {
        return seq.length();
    }

    public void addPredecessor(BasicBlock block) {
        preds.add(block);
    }

    public void removePredecessor(BasicBlock block) {
        while (preds.remove(block)) /**/ ;
    }

    public void addSuccessor(BasicBlock block) {
        succs.add(block);
        block.addPredecessor(this);
    }

    public void removeSuccessor(BasicBlock block) {
        while (succs.remove(block)) /**/ ;
        block.removePredecessor(this);
    }

    // FIXME: unify block comparisons: id or direct equality
    public void replaceSuccessor(BasicBlock oldBlock, BasicBlock newBlock) {
        for (int i = 0; i < succs.size(); i++) {
            if (succs.get(i).id == oldBlock.id) {
                succs.set(i, newBlock);
                oldBlock.removePredecessor(this);
                newBlock.addPredecessor(this);
            }
        }

        for (int i = 0; i < succExceptions.size(); i++) {
            if (succExceptions.get(i).id == oldBlock.id) {
                succExceptions.set(i, newBlock);
                oldBlock.removePredecessorException(this);
                newBlock.addPredecessorException(this);
            }
        }
    }

    public void addPredecessorException(BasicBlock block) {
        predExceptions.add(block);
    }

    public void removePredecessorException(BasicBlock block) {
        while (predExceptions.remove(block)) /**/ ;
    }

    public void addSuccessorException(BasicBlock block) {
        if (!succExceptions.contains(block)) {
            succExceptions.add(block);
            block.addPredecessorException(this);
        }
    }

    public void removeSuccessorException(BasicBlock block) {
        while (succExceptions.remove(block)) /**/ ;
        block.removePredecessorException(this);
    }

    public String toString() {
        return toString(0);
    }

    public String toString(int indent) {

        String new_line_separator = DecompilerContext.getNewLineSeparator();

        return id + ":" + new_line_separator + seq.toString(indent);
    }

    public boolean isSuccessor(BasicBlock block) {
        for (BasicBlock succ : succs) {
            if (succ.id == block.id) {
                return true;
            }
        }
        return false;
    }

    // *****************************************************************************
    // getter and setter methods
    // *****************************************************************************

    public List<Integer> getInstrOldOffsets() {
        return instrOldOffsets;
    }

    @Override
    public List<? extends IGraphNode> getPredecessors() {
        List<BasicBlock> lst = new ArrayList<>(preds);
        lst.addAll(predExceptions);
        return lst;
    }

    public List<BasicBlock> getPreds() {
        return preds;
    }

    public InstructionSequence getSeq() {
        return seq;
    }

    public void setSeq(InstructionSequence seq) {
        this.seq = seq;
    }

    public List<BasicBlock> getSuccs() {
        return succs;
    }

    public List<BasicBlock> getSuccExceptions() {
        return succExceptions;
    }

    public List<BasicBlock> getPredExceptions() {
        return predExceptions;
    }
}